﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace SyncUdp
{
    using SyncUdp.Event.GameEvent;
    using SyncUdp.Event.NetworkEvent;
    using SyncUdp.Interface;
    using SyncUdp.MethodExtension;
    using SyncUdp.Network;

    public interface IPlayerSystem : ISingleton
    {
        public void CloseGame();
    }

    /// <summary>
    /// 负责与玩家数据与表现之间的交互
    /// </summary>
    public class PlayerSystem : AbstractSingleton, IPlayerSystem
    {
        IGameManager _gm;
        INetworkSystem _network;
        IResourceManager _resMgr;
        Dictionary<int, PlayerController> _players;

        void CheckPlayerState(List<ClientInputsEvent> frame)
        {
            foreach (var input in frame)
            {
                // 若不存在某一玩家就实例化
                // 网络不好的情况下就会导致延迟接受别人的加入
                if (_players.ContainsKey(input.id) == false)
                {
                    if (input.id == _network.PlayerId)
                        continue;
                    var prefab = _resMgr.Load<GameObject>(_gm.AllPaths.playerPrefabResPath);
                    var ctrl = OnPlayerPrefabLoaded(prefab, input.id);
                    ctrl.PlayerName = input.name;
                }
                // 如果断连了则隐藏并分发事件
                else if (input.isDisconnected)
                {
                    var parent = _players[input.id].transform.parent;
                    if (parent.gameObject.activeSelf)
                    {
                        parent.gameObject.SetActive(false);
                        SendEvent(new OtherPlayerDisconnectedEvent() { otherPlayerName = parent.name });
                    }
                }
                // 没有断连但该玩家为隐藏状态
                else if (input.id != _network.PlayerId)
                {
                    var parent = _players[input.id].transform.parent;
                    if (parent.gameObject.activeSelf == false)
                    {
                        parent.gameObject.SetActive(true);
                        SendEvent(new OtherPlayerJoinedGameEvent { otherPlayerName = parent.name });
                    }
                }
            }
        }

        void UpdatePhysics(List<ClientInputsEvent> frame)
        {
            // 即使其他玩家没有输入也要更新，使控制器更新至最新状态
            foreach (var pair in _players)
            {
                var input = frame.Find(i => i.id == pair.Key);
                bool isNull = input == null || input.inputs == null;
                var args = new UpdatePlayerPhysicsArgs()
                {
                    isToPredict = false, 
                    isSprinting = isNull ? false : input.inputs.isSprinting,
                    movementInput = isNull ? Vector2Int.zero : input.inputs.movementInput,
                };
                pair.Value.UpdatePhysics(args);
            }
        }

        void UpdateAllPlayerControllers(List<ClientInputsEvent> frame)
        {
            CheckPlayerState(frame);
            UpdatePhysics(frame);
        }

        #region 方法实现
        protected override void OnInit()
        {
            _gm = this.GetInstance<IGameManager>();
            _network = this.GetInstance<INetworkSystem>();
            _resMgr = this.GetInstance<IResourceManager>();
            _players = new Dictionary<int, PlayerController>();

            RegisterEvent<GameCreatedSuccessfullyEvent>(OnGameHasCreated);
            RegisterEvent<ClientHasReceivedInputsEvent>(OnClientHasReceivedInputs);
            //RegisterEvent<GameJoinedSuccessfullyEvent>(OnJoinedGame);
        }

        public void CloseGame()
        {
            foreach (var player in _players.Values)
            {
                var parent = player.transform.parent.gameObject;
                parent.SetActive(false);
                GameObject.Destroy(parent);
            }
            _players.Clear();
            // 显示滑鼠光标
            Cursor.visible = true;
            // 并将光标解除锁定
            Cursor.lockState = CursorLockMode.None;
        }
        #endregion

        #region 回调方法
        void OnGameHasCreated(GameCreatedSuccessfullyEvent e)
        {
            _resMgr.LoadAsync<GameObject>(_gm.AllPaths.playerPrefabResPath, prefab =>
            {
                // 隐藏滑鼠光标
                Cursor.visible = false;
                // 将光标锁定到游戏窗口的中心
                Cursor.lockState = CursorLockMode.Locked;
                var ctrl = OnPlayerPrefabLoaded(prefab, _network.PlayerId);
                ctrl.PlayerName = _network.PlayerName;
                ctrl.ReceiveInputs();
            });
        }

        void OnClientHasReceivedInputs(ClientHasReceivedInputsEvent e)
        {
            // 追帧完毕且没有创建本机玩家，则异步创建
            if (!e.isToReissue && !_players.ContainsKey(_network.PlayerId))
                OnGameHasCreated(new GameCreatedSuccessfullyEvent());
            // 防止服务器又发来输入时导致接收两次本地输入；只适用于追帧的情况。
            bool flag = false;
            // 更新完所有玩家的物理
            while (e.inputsCache.Count > 0)
            {
                e.inputsCache.TryDequeue(out var frame);
                _network.CurFrame++;
                flag = true;
                UpdateAllPlayerControllers(frame);
            }
            // 仅接收当前玩家的输入
            if (flag && !e.isToReissue && _players.TryGetValue(_network.PlayerId, out var player))
                player.ReceiveInputs();
        }

        [System.Obsolete]
        void OnJoinedGame(GameJoinedSuccessfullyEvent e)
        {
            OnGameHasCreated(new GameCreatedSuccessfullyEvent());
            for (int i = 0; i < e.otherIds.Count; i++)
            {
                if (e.otherIds[i] == _network.PlayerId)
                    continue;
                int id = e.otherIds[i];
                string name = e.otherNames[i];
                _resMgr.LoadAsync<GameObject>(_gm.AllPaths.playerPrefabResPath, prefab =>
                {
                    var ctrl = OnPlayerPrefabLoaded(prefab, id);
                    ctrl.PlayerName = name;
                });
            }
        }

        PlayerController OnPlayerPrefabLoaded(GameObject prefab, int playerId)
        {
            var go = GameObject.Instantiate(prefab);
            var ctrl = go.GetComponentInChildren<PlayerController>();
            _players.TryAdd(playerId, ctrl);
            // 隐藏相机
            var cinemachine = go.GetComponentInChildren<Cinemachine.CinemachineVirtualCamera>();
            cinemachine.gameObject.SetActive(playerId == _network.PlayerId);
            return ctrl;
        }
        #endregion
    }
}
